package com.xzy.urp.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.xzy.urp.handle.TimingHandle;
import com.xzy.urp.pojo.entity.CourseSelection;
import com.xzy.urp.mapper.CourseSelectionMapper;
import com.xzy.urp.pojo.vo.CourseInfoVO;
import com.xzy.urp.pojo.vo.CourseSelectableVO;
import com.xzy.urp.pojo.vo.GradeVO;
import com.xzy.urp.pojo.vo.StudentSelectVO;
import com.xzy.urp.service.CourseInfoService;
import com.xzy.urp.service.CourseSelectionService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xzy.urp.service.mq.MQSender;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.BoundListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author xzy
 * @since 2022-02-17
 */
@Service
@Slf4j
public class CourseSelectionServiceImpl extends ServiceImpl<CourseSelectionMapper, CourseSelection> implements CourseSelectionService {


    RedisTemplate<String, Object> template;

    CourseInfoService courseInfoService;

    MQSender mqSender;


    public CourseSelectionServiceImpl(RedisTemplate<String, Object> template, CourseInfoService courseInfoService, MQSender mqSender) {
        this.template = template;
        this.courseInfoService = courseInfoService;
        this.mqSender = mqSender;
    }

    /**
     * //如何安全获取到可选列表
     * //1 若redis有 redis获取
     * //2 若redis中无 数据库获取
     * //3 获取到后，redis获取课余量添加入list （因为不管是redis还是数据库中获取到的课余量都是不同步的）
     * //4 虽然获取到了redis中同步的课余量，但是选课时还是得判断课余量，保证并发安全性（因为用户获取到后，不会原子的并发扣库存）
     * //5 每次选课完成后，都要同步redis的可选课表
     */
    @Override
    public List<CourseSelectableVO> getSelectableList(StudentSelectVO studentSelectVO) {
        String key = "urp:core:selectable:" + studentSelectVO.getUId();
        List<CourseSelectableVO> selectableVOList;
        BoundHashOperations<String, String, CourseSelectableVO> mapBound = template.boundHashOps(key);
        try {
            selectableVOList = mapBound.values();
            if (selectableVOList != null && !selectableVOList.isEmpty()) {
                //存在则取出
                return selectableVOList;
            }
        } catch (Exception e) {
            log.error("redis服务器异常：" + ExceptionUtils.getStackTrace(e));//此处不抛出异常，继续执行后面的代码
        }
        //此处为不存在
        selectableVOList = baseMapper.getSelectableList
                (studentSelectVO.getGrade(), studentSelectVO.getDeptId(), studentSelectVO.getYear(), studentSelectVO.getUId());
        try {
            //将查到的所有数据存入redis
            selectableVOList.forEach(e -> {
                //hashKey为课程id
                mapBound.put(e.getCId().toString(), e);
            });
            if (selectableVOList.isEmpty()){
                mapBound.put("0",null);
            }
            // todo: 设置过期时间
        } catch (Exception e) {
            log.error("redis服务器异常：" + ExceptionUtils.getStackTrace(e));//此处不抛出异常，继续执行后面的代码
        }
        //todo: redis中查出课余量放入
        return selectableVOList;
    }

    /**
     * //1 若redis有 redis获取
     * //2 若redis中无 数据库获取
     * //每次选课操作完成后，直接同步redis，与数据库
     */
    @Override
    public List<CourseSelectableVO> getSelectedList(Integer semester, Long sId) {
        String key = "urp:core:selected:" + sId;
        List<CourseSelectableVO> selectableVOList;
        BoundListOperations<String, Object> listBound = template.boundListOps(key);
        try {
            selectableVOList = (List<CourseSelectableVO>) (Object) listBound.range(0, -1);
            if (selectableVOList != null && !selectableVOList.isEmpty()) {
                //存在则取出
                return selectableVOList;
            }
        } catch (Exception e) {
            log.error("redis服务器异常：" + ExceptionUtils.getStackTrace(e));//此处不抛出异常，继续执行后面的代码
        }
        //此处为不存在
        selectableVOList = baseMapper.getSelectedList(semester, sId);
        try {
            //将查到的所有数据存入redis
            CourseSelectableVO[] a = new CourseSelectableVO[selectableVOList.size()];
            selectableVOList.toArray(a);
            if (a.length!=0){
                listBound.leftPushAll(a);
            }else{
                listBound.leftPush(null);
            }
            // todo: 设置过期时间
        } catch (Exception e) {
            log.error("redis服务器异常：" + ExceptionUtils.getStackTrace(e));//此处不抛出异常，继续执行后面的代码
        }
        return selectableVOList;
    }

    /**
     * 访问控制锁，应该控制同一时间一个账号只有一个请求，因为校验可选课程需要强数据一致性
     */
    @Override
    public Boolean doSelect(StudentSelectVO studentSelectVO, Long cId) throws Exception{
        String selectableKey = "urp:core:selectable:" + studentSelectVO.getUId();
        String CountKey = "urp:core:count";
        String selectedKey = "urp:core:selected:" + studentSelectVO.getUId();
        BoundHashOperations<String, String, CourseSelectableVO> selectBound = template.boundHashOps(selectableKey);
        BoundHashOperations<String, String, Integer> countBound = template.boundHashOps(CountKey);
        //map中获取是否可选
        getSelectableList(studentSelectVO);//保证redis中缓存存在
        getSelectedList(studentSelectVO.getYear(), studentSelectVO.getUId());

        CourseSelectableVO courseSelectableVO = selectBound.get(cId.toString());

        if (courseSelectableVO == null) {
            //不可选
            return false;
        }


        //预检库存
        Integer stock = countBound.get(cId.toString());
        if (null == stock) {
            //课程不存在
             log.info("课程不存在");
            return false;
        }
        if (stock <= 0) {
            //库存不足
             log.info("预检时库存不足");
            return false;
        }
        //此处库存充足
        //通过后redis原子减库存  异步同步mysql
        Long increment = countBound.increment(cId.toString(), -1);
        if (increment >= 0) {//这里为选课成功
            //PS：删除缓存而不是更新缓存
            CourseSelection courseSelection = new CourseSelection();
            courseSelection.setCid(cId);
            courseSelection.setSid(studentSelectVO.getUId());
            //这里的插入选课表需要同步
            baseMapper.insert(courseSelection);
            //修改库存可以异步进行，因为库存判断是在redis中进行，不需要考虑数据一致性
            mqSender.decrTicket(cId);

            template.delete(selectedKey);
            template.delete(selectableKey);
            return true;
        } else {
            //秒杀失败，在此前第一次查看库存与第二次原子减库存之间有线程修改库存导致库存不足了
            //为了保证数据的线程安全性，需要回退数据，将之前减去的redis中库存回退
            countBound.increment(cId.toString(), 1);
            return false;
        }


    }

    /**
     * redis添加控制锁，保证数据强一致性
     *
     *
     *
     */
    @Override
    public Boolean cancelSelect(StudentSelectVO studentSelectVO, Long cId) {
        String CountKey = "urp:core:count";
        String selectedKey = "urp:core:selected:" + studentSelectVO.getUId();
        String selectableKey = "urp:core:selectable:" + studentSelectVO.getUId();
        BoundHashOperations<String, String, Integer> countBound = template.boundHashOps(CountKey);
        BoundListOperations<String, Object> selectedBound = template.boundListOps(selectedKey);
        getSelectableList(studentSelectVO);//保证redis中缓存存在
        getSelectedList(studentSelectVO.getYear(), studentSelectVO.getUId());

        //获取已选择表

        List<CourseSelectableVO> selectableVOList = (List<CourseSelectableVO>) (Object) selectedBound.range(0, -1);



        boolean flag =false ;
        if (selectableVOList != null){
            for (int i = 0; i < selectableVOList.size(); i++) {
                CourseSelectableVO o = (CourseSelectableVO) selectableVOList.get(i);
                if(o.getCId().equals(cId)){
                    flag = true;
                }
            }
        }else { //没有此表
            return  false;
        }
        if (!flag){
            return false;
        }


        //redis中原子增加库存  异步同步mysql
        countBound.increment(cId.toString(), 1);

        log.info("a");
        mqSender.incrTicket(cId);
        //同步删除已选择课表
        QueryWrapper<CourseSelection> wrapper = new QueryWrapper<>();
        wrapper.eq("sid",studentSelectVO.getUId()).eq("cid",cId);
        log.info(studentSelectVO.getUId().toString());
        log.info(cId.toString());
        baseMapper.delete(wrapper);
        log.info("b");
        //删除redis
        template.delete(selectedKey);
        template.delete(selectableKey);

        return true;
    }

    @Override
    public Boolean startOperation(Integer semester) {
        String key = "urp:core:count";
        List<CourseInfoVO> courseInfoVOS = courseInfoService.listSurplusBySemester(semester);
        Map<String, Integer> countMap = new HashMap<>();
        courseInfoVOS.forEach(e -> {
            countMap.put(e.getId().toString(), e.getSurplus());
        });
        BoundHashOperations mapBound = template.boundHashOps(key);
        mapBound.putAll(countMap);
        //过滤器开启放行
        TimingHandle.getTimingHandle().start();
        //System.out.println(mapBound.get("1"));
        return null;
    }

    @Override
    public Boolean endOperation() {
        //清除所有键值对
        Set<String> keys = template.keys("urp:core:*");
        template.delete(keys);
        //过滤器停止放行
        TimingHandle.getTimingHandle().over();
        return null;
    }

    @Override
    public void gradeEntry(Long sId ,Double grade, Integer CId) {
        UpdateWrapper<CourseSelection> wrapper = new UpdateWrapper<>();
        System.out.println(grade+"-------------------");
        wrapper.eq("sid", sId).eq("cid", CId).set("grade", grade);
        this.update(wrapper);
    }

    @Override
    public List<GradeVO> ListGrade(Integer cId) {
        return baseMapper.listByCid(cId);

    }


}
